﻿namespace SPDeploy.Library.Helpers
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.IO;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Text.RegularExpressions;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Administration;
    using Microsoft.SharePoint.Utilities;

    public class AdminServiceTools
    {
        #region AdmJobsStatus enum

        public enum AdmJobsStatus
        {
            TryAgain,
            Error,
            Success
        }

        #endregion

        #region Methods: public

        public static void RecycleOwssvr()
        {
            PropertyInfo threadCtx = typeof (SPFarm).GetProperty("ThreadContext", BindingFlags.Static | BindingFlags.NonPublic);
            var tbl = (Hashtable) threadCtx.GetValue(null, null);
            PropertyInfo requestProp = typeof (SPFarm).GetProperty("RequestNoAuth", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            requestProp.GetValue(SPFarm.Local, null);
            tbl.Remove(typeof (SPFarm).Assembly.GetType("Microsoft.SharePoint.SPRequestManager"));

            IntPtr p = GetModuleHandle("OWSSVR.DLL");
            FreeLibrary(p);
            string stsadmPath = SPUtility.GetGenericSetupPath("ISAPI");
            p = LoadLibrary(stsadmPath + @"\OWSSVR.DLL");
        }

        public static AdmJobsStatus ExecAdmSvcJobs()
        {
            try
            {
                string stsadmPath = SPUtility.GetGenericSetupPath("BIN");
                Assembly stsadmAsm = Assembly.LoadFile(Path.GetFullPath(stsadmPath + "\\STSADM.EXE"));
                Type admSvcJobType = stsadmAsm.GetType("Microsoft.SharePoint.StsAdmin.SPExecuteAdministrationServiceJobs");
                ConstructorInfo ci = admSvcJobType.GetConstructor(new[] {typeof (SPGlobalAdmin)});
                TextWriter oldOut = Console.Out;
                var outputWriter = new StringWriter();
                Console.SetOut(outputWriter);
                try
                {
                    using (var admin = new SPGlobalAdmin())
                    {
                        object svcjobs = ci.Invoke(new object[] {admin});
                        MethodInfo runMi = admSvcJobType.GetMethod("Run", BindingFlags.Instance | BindingFlags.Public);
                        runMi.Invoke(svcjobs, new object[] {new StringDictionary()});
                    }
                }
                finally
                {
                    Console.SetOut(oldOut);
                }
                string output = outputWriter.ToString();
                if (Regex.IsMatch(output, GetRegexMatchFromSPResourceString("ExceptionWhileCleaningAdminServiceJob")))
                {
                    return AdmJobsStatus.TryAgain;
                }
                if (Regex.IsMatch(output, GetRegexMatchFromSPResourceString("ExceptionWhileExecutingAdminServiceJob")))
                {
                    return AdmJobsStatus.Error;
                }
                return AdmJobsStatus.Success;
            }
            catch
            {
                return AdmJobsStatus.Error;
            }
        }

        public static void PreformIisCleanup(SPWebApplication webApp, bool deleteWebSite, IEnumerable<SPIisSettings> settings)
        {
            var serverComments = new List<string>();
            var vdirs = new List<string>();
            foreach (SPIisSettings iisSetting in settings)
            {
                vdirs.Add(iisSetting.Path.ToString());
                serverComments.Add(iisSetting.ServerComment);
            }

            // webApp.Unprovision() does not allow us to specify whether we want the site deleted so we have to use the internal version.
            // SPWebApplication.UnprovisionIisWebSites(deleteIis, serverComments, webApp.ApplicationPool.Name);
            MethodInfo unprovisionIisWebSites = webApp.GetType().GetMethod("UnprovisionIisWebSites",
                                                                           BindingFlags.NonPublic | BindingFlags.Public |
                                                                           BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Static,
                                                                           null, new[] {typeof (bool), typeof (string[]), typeof (string)}, null);
            unprovisionIisWebSites.Invoke(webApp, new object[] {deleteWebSite, serverComments.ToArray(), webApp.ApplicationPool.Name});

            // SPSolution.RetractSolutions(webApp.Farm, webApp.Id, vdirs, serverComments, true);
            MethodInfo retractSolutions = typeof (SPSolution).GetMethod("RetractSolutions",
                                                                        BindingFlags.NonPublic | BindingFlags.Public |
                                                                        BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Static,
                                                                        null, new[] {typeof (SPFarm), typeof (Guid), typeof (string[]), typeof (string[]), typeof (bool)}, null);

            retractSolutions.Invoke(null, new object[] {webApp.Farm, webApp.Id, vdirs.ToArray(), serverComments.ToArray(), true});

            if (SPFarm.Local.TimerService.Instances.Count > 1)
            {
                // SPIisWebsiteUnprovisioningJobDefinition is an internal class so we need to use reflection to set it.
                // SPIisWebsiteUnprovisioningJobDefinition jobDef = new SPIisWebsiteUnprovisioningJobDefinition(deleteIis, serverComments, webApp.ApplicationPool.Name, vdirs, webApp.Id, true);
                Type sPIisWebsiteUnprovisioningJobDefinitionType =
                    Type.GetType(
                        "Microsoft.SharePoint.Administration.SPIisWebsiteUnprovisioningJobDefinition, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
                ConstructorInfo sPIisWebsiteUnprovisioningJobDefinitionConstructor =
                    sPIisWebsiteUnprovisioningJobDefinitionType.GetConstructor(
                        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
                        null,
                        new[] {typeof (bool), typeof (string[]), typeof (string), typeof (string[]), typeof (Guid), typeof (bool)}, null);
                object jobDef =
                    sPIisWebsiteUnprovisioningJobDefinitionConstructor.Invoke(new object[]
                                                                                  {
                                                                                      deleteWebSite, serverComments.ToArray(), webApp.ApplicationPool.Name, vdirs.ToArray(), webApp.Id,
                                                                                      true
                                                                                  });
                // jobDef.Schedule = new SPOneTimeSchedule(DateTime.Now);
                PropertyInfo scheduleProp = sPIisWebsiteUnprovisioningJobDefinitionType.GetProperty("Schedule",
                                                                                                    BindingFlags.FlattenHierarchy |
                                                                                                    BindingFlags.NonPublic |
                                                                                                    BindingFlags.Instance |
                                                                                                    BindingFlags.InvokeMethod |
                                                                                                    BindingFlags.GetProperty |
                                                                                                    BindingFlags.Public);
                scheduleProp.SetValue(jobDef, new SPOneTimeSchedule(DateTime.Now), null);

                // jobDef.Update();
                MethodInfo update = sPIisWebsiteUnprovisioningJobDefinitionType.GetMethod("Update",
                                                                                          BindingFlags.NonPublic |
                                                                                          BindingFlags.Public |
                                                                                          BindingFlags.Instance |
                                                                                          BindingFlags.InvokeMethod |
                                                                                          BindingFlags.FlattenHierarchy,
                                                                                          null,
                                                                                          new Type[] {}, null);
                update.Invoke(jobDef, new object[] {});
            }
        }

        #endregion

        #region Methods: private

        [DllImport("kernel32.dll")]
        private static extern IntPtr FreeLibrary(IntPtr library);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private static string GetRegexMatchFromSPResourceString(string resourceName)
        {
            string s = SPResource.GetString(resourceName, new[] {"{0}", "{0}", "{0}"});
            s = s.Replace(".", @"\.");
            return string.Format(s, "[^ ]+");
        }

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        #endregion
    }
}